﻿/*
* Copyright (c) 2010-2012 Tesla Engine Group
* 
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* 
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
* 
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/


using System;
using System.Globalization;
using System.Runtime.InteropServices;

namespace Tesla.Math {
    /// <summary>
    /// Defines a rectangle.
    /// </summary>
    [Serializable]
    [StructLayout(LayoutKind.Sequential)]
    public struct Rectangle : IEquatable<Rectangle> {
        /// <summary>
        /// Top-left X coordinate of the rectangle
        /// </summary>
        public int X;
        
        /// <summary>
        /// Top-left Y coordinate of the rectangle
        /// </summary>
        public int Y;

        /// <summary>
        /// Width of the rectangle
        /// </summary>
        public int Width;

        /// <summary>
        /// Height of the rectangle
        /// </summary>
        public int Height;

        private static Rectangle _empty = new Rectangle();

        /// <summary>
        /// Get the center of the rectangle.
        /// </summary>
        public Point Center {
            get {
                return new Point(this.X + (this.Width / 2), this.Y + (this.Height / 2));
            }
        }

        /// <summary>
        /// Get the top-left hand corner (X,Y values).
        /// </summary>
        public Point TopLeftLocation {
            get {
                return new Point(this.X, this.Y);
            }
            set {
                this.X = value.X;
                this.Y = value.Y;
            }
        }

        /// <summary>
        /// Get the left-most X coordinate.
        /// </summary>
        public int Left {
            get {
                return this.X;
            }
        }

        /// <summary>
        /// Get the right-most X coordinate (Left + Width).
        /// </summary>
        public int Right {
            get {
                return this.X + this.Width;
            }
        }

        /// <summary>
        /// Get the top-most Y coordinate.
        /// </summary>
        public int Top {
            get {
                return this.Y;
            }
        }

        /// <summary>
        /// Get the bottom-most Y coordinate (Top + Height).
        /// </summary>
        public int Bottom {
            get {
                return this.Y + this.Height;
            }
        }

        /// <summary>
        /// Get the empty rectangle, where the top left corner is defined at the origin
        /// and the rectangle has no area.
        /// </summary>
        public static Rectangle Empty {
            get {
                return _empty;
            }
        }

        /// <summary>
        /// Get if this rectangle is empty (zero area, origin for top left corner) or not.
        /// </summary>
        public bool IsEmpty {
            get {
                return this.X == 0 && this.Y == 0 && this.Width == 0 && this.Height == 0;
            }
        }

        /// <summary>
        /// Creates a new Rectangle.
        /// </summary>
        /// <param name="x">Top left X-coordinate</param>
        /// <param name="y">Top left Y-coordinate</param>
        /// <param name="width">Width of the rectangle</param>
        /// <param name="height">Height of the rectangle</param>
        public Rectangle(int x, int y, int width, int height) {
            this.X = x;
            this.Y = y;
            this.Width = width;
            this.Height = height;
        }

        /// <summary>
        /// Translates this rectangle's top left X,Y coordinates by the supplied amount.
        /// </summary>
        /// <param name="p">Amount to translate</param>
        public void Translate(Point p) {
            this.X += p.X;
            this.Y += p.Y;
        }

        /// <summary>
        /// Translates this rectangle's top left X,Y coordinates by the supplied amount.
        /// </summary>
        /// <param name="xAmount">Amount to translate in X axis</param>
        /// <param name="yAmount">Amount to translate in Y axis</param>
        public void Translate(int xAmount, int yAmount) {
            this.X += xAmount;
            this.Y += yAmount;
        }

        /// <summary>
        /// Scale the rectangle by the supplied amount.
        /// </summary>
        /// <param name="xAmount">Amount to scale in X axis</param>
        /// <param name="yAmount">Amount to scale in Y axis</param>
        public void Scale(int xAmount, int yAmount) {
            this.X -= xAmount;
            this.Y -= yAmount;

            this.Width += xAmount * 2;
            this.Height += yAmount * 2;
        }

        /// <summary>
        /// Test if the supplied point is inside this rectangle.
        /// </summary>
        /// <param name="p">Point to test</param>
        /// <returns>True if contained by rectangle</returns>
        public bool Contains(Point p) {
            return ((this.X <= p.X) && (p.X < (this.X + this.Width)))
                && ((this.Y <= p.Y) && (p.Y < (this.Y + this.Height)));
        }

        /// <summary>
        /// Test if the supplied point are inside this rectangle.
        /// </summary>
        /// <param name="p">Point to test</param>
        /// <param name="result">True if contained by rectangle</param>
        public void Contains(ref Point p, out bool result) {
            result = ((this.X <= p.X) && (p.X < (this.X + this.Width)))
                && ((this.Y <= p.Y) && (p.Y < (this.Y + this.Height)));
        }

        /// <summary>
        /// Test if the supplied coordinates are inside this rectangle.
        /// </summary>
        /// <param name="x">X coord</param>
        /// <param name="y">Y coord</param>
        /// <returns>True if contained</returns>
        public bool Contains(int x, int y) {
            return ((this.X <= x) && (x < (this.X + this.Width)))
                && ((this.Y <= y) && (y < (this.Y + this.Height)));
        }

        /// <summary>
        /// Test if the supplioed rectangle is inside of this rectangle.
        /// </summary>
        /// <param name="rect">Rectangle to test</param>
        /// <returns>True if the rectangle is contained</returns>
        public bool Contains(Rectangle rect) {
            return ((this.X <= rect.X) && ((rect.X + rect.Width) <= (this.X + this.Width)))
                && ((this.Y <= rect.Y) && ((rect.Y + rect.Height) <= (this.Y + this.Height)));
        }

        /// <summary>
        /// Test if the supplioed rectangle is inside of this rectangle.
        /// </summary>
        /// <param name="rect">Rectangle to test</param>
        /// <param name="result">True if rectangle is contained</param>
        public void Contains(ref Rectangle rect, out bool result) {
            result = ((this.X <= rect.X) && ((rect.X + rect.Width) <= (this.X + this.Width)))
                && ((this.Y <= rect.Y) && ((rect.Y + rect.Height) <= (this.Y + this.Height)));
        }

        /// <summary>
        /// Create the union between two rectangles.
        /// </summary>
        /// <param name="a">First rectangle</param>
        /// <param name="b">Second rectangle</param>
        /// <returns>Merged rectangle</returns>
        public static Rectangle Union(Rectangle a, Rectangle b) {
            Rectangle rectangle;

            //Top xAxis x coords for both
            int trx1 = a.X + a.Width;
            int trx2 = b.X + b.Width;

            //Bottom left y coords for both
            int bly1 = a.Y + a.Height;
            int bly2 = b.Y + b.Height;

            //Get the top left corner of new rectangle
            int topLeftX = (a.X < b.X) ? a.X : b.X;
            int topLeftY = (a.Y < b.Y) ? a.Y : b.Y;

            //Get the maximum topright x, and bottom left y coordinates 
            int topRightX = (trx1 > trx2) ? trx1 : trx2;
            int botLeftY = (bly1 > bly2) ? bly1 : bly2;

            rectangle.X = topLeftX;
            rectangle.Y = topLeftY;
            rectangle.Width = topRightX - topLeftX;
            rectangle.Height = botLeftY - topLeftY;

            return rectangle;
        }

        /// <summary>
        /// Create the union between two rectangles.
        /// </summary>
        /// <param name="a">First rectangle</param>
        /// <param name="b">Second rectangle</param>
        /// <param name="result">Existing rectangle to hold result</param>
        public static void Union(ref Rectangle a, ref Rectangle b, out Rectangle result) {
            //Top xAxis x coords for both
            int trx1 = a.X + a.Width;
            int trx2 = b.X + b.Width;

            //Bottom left y coords for both
            int bly1 = a.Y + a.Height;
            int bly2 = b.Y + b.Height;

            //Get the top left corner of new rectangle
            int topLeftX = (a.X < b.X) ? a.X : b.X;
            int topLeftY = (a.Y < b.Y) ? a.Y : b.Y;

            //Get the maximum topright x, and bottom left y coordinates 
            int topRightX = (trx1 > trx2) ? trx1 : trx2;
            int botLeftY = (bly1 > bly2) ? bly1 : bly2;

            result.X = topLeftX;
            result.Y = topLeftY;
            result.Width = topRightX - topLeftX;
            result.Height = botLeftY - topLeftY;
        }

        /// <summary>
        /// Tests if a rectangle intersects with this one.
        /// </summary>
        /// <param name="value">Rectangle to test against</param>
        /// <returns>True if they intersect</returns>
        public bool Intersects(Rectangle value) {
            return ((value.X < (this.X + this.Width)) && (this.X < (value.X + value.Width))) 
                && ((value.Y < (this.Y + this.Height)) && (this.Y < (value.Y + value.Height)));
        }

        /// <summary>
        /// Tests if a rectangle intersects with this one.
        /// </summary>
        /// <param name="value">Rectangle to test against</param>
        /// <param name="result">True if they intersect</param>
        public void Intersects(ref Rectangle value, out bool result) {
            result = ((value.X < (this.X + this.Width)) && (this.X < (value.X + value.Width))) 
                && ((value.Y < (this.Y + this.Height)) && (this.Y < (value.Y + value.Height)));
        }

        /// <summary>
        /// Finds the rectangle that represents the overlap (intersection) of
        /// two rectangles. May return empty
        /// </summary>
        /// <param name="a">First rectangle</param>
        /// <param name="b">Second rectangle</param>
        /// <returns>Their intersection</returns>
        public static Rectangle Intersect(Rectangle a, Rectangle b) {
            //Top xAxis x coords for both
            int trx1 = a.X + a.Width;
            int trx2 = b.X + b.Width;

            //Bottom left y coords for both
            int bly1 = a.Y + a.Height;
            int bly2 = b.Y + b.Height;

            //Get the top left corner of new rectangle
            int topLeftX = (a.X > b.X) ? a.X : b.X;
            int topLeftY = (a.Y > b.Y) ? a.Y : b.Y;

            //Get the minimum topright x, and bottom left y coordinates 
            int topRightX = (trx1 < trx2) ? trx1 : trx2;
            int botLeftY = (bly1 < bly2) ? bly1 : bly2;

            //Ensure if these are greater than the top left corner, if not
            //we have a rectangle with negative width/height which is invalid since
            //they dont intersect then!
            if((topRightX > topLeftX) && (botLeftY > topLeftY)) {
                Rectangle rectangle;
                rectangle.X = topLeftX;
                rectangle.Y = topLeftY;
                rectangle.Width = topRightX - topLeftX;
                rectangle.Height = botLeftY - topLeftY;
                return rectangle;
            }
            return Rectangle.Empty;
        }

        /// <summary>
        /// Finds the rectangle that represents the overlap (intersection) of
        /// two rectangles. May return empty
        /// </summary>
        /// <param name="a">First rectangle</param>
        /// <param name="b">Second rectangle</param>
        /// <param name="result">Existing rectangle to hold result</param>
        public static void Intersect(ref Rectangle a, ref Rectangle b, out Rectangle result) {
            //Top xAxis x coords for both
            int trx1 = a.X + a.Width;
            int trx2 = b.X + b.Width;

            //Bottom left y coords for both
            int bly1 = a.Y + a.Height;
            int bly2 = b.Y + b.Height;

            //Get the top left corner of new rectangle
            int topLeftX = (a.X > b.X) ? a.X : b.X;
            int topLeftY = (a.Y > b.Y) ? a.Y : b.Y;

            //Get the minimum topright x, and bottom left y coordinates 
            int topRightX = (trx1 < trx2) ? trx1 : trx2;
            int botLeftY = (bly1 < bly2) ? bly1 : bly2;

            //Ensure if these are greater than the top left corner, if not
            //we have a rectangle with negative width/height which is invalid since
            //they dont intersect then!
            if((topRightX > topLeftX) && (botLeftY > topLeftY)) {
                result.X = topLeftX;
                result.Y = topLeftY;
                result.Width = topRightX - topLeftX;
                result.Height = botLeftY - topLeftY;
            }

            result = Rectangle.Empty;
        }

        /// <summary>
        /// Tests equality between this rectangle and another.
        /// </summary>
        /// <param name="other">Rectangle to test against</param>
        /// <returns>True if equal</returns>
        public bool Equals(Rectangle other) {
            return (this.X == other.X) && (this.Y == other.Y) 
                && (this.Width == other.Width) && (this.Height == other.Height);
        }

        /// <summary>
        /// Test equality between this rectangle and the supplied object.
        /// </summary>
        /// <param name="obj">Object to test against</param>
        /// <returns>True if equal</returns>
        public override bool Equals(Object obj) {
            if(obj is Rectangle) {
                return Equals((Rectangle) obj);
            }
            return false;
        }

        /// <summary>
        /// Get the hashcode of this rectangle.
        /// </summary>
        /// <returns>Hash code</returns>
        public override int GetHashCode() {
            return this.X.GetHashCode() + this.Y.GetHashCode() + this.Width.GetHashCode() + this.Height.GetHashCode();
        }

        /// <summary>
        /// Get the string representation of this rectangle.
        /// </summary>
        /// <returns>String representation</returns>
        public override string ToString() {
            CultureInfo info = CultureInfo.CurrentCulture;
            return String.Format(info, "{{X:{0} Y:{1} Width:{2} Height:{3}}}",
                new Object[] { this.X.ToString(info), this.Y.ToString(info), this.Width.ToString(info), this.Height.ToString(info) });
        }

        /// <summary>
        /// Test equality between two rectangles.
        /// </summary>
        /// <param name="a">First rectangle</param>
        /// <param name="b">Second rectangle</param>
        /// <returns>True if equal</returns>
        public static bool operator ==(Rectangle a, Rectangle b){
            return (a.X == b.X) && (a.Y == b.Y) && (a.Width == b.Width) && (a.Height == b.Height);
        }

        /// <summary>
        /// Test inequality between two rectangles.
        /// </summary>
        /// <param name="a">First rectangle</param>
        /// <param name="b">Second rectangle</param>
        /// <returns>True if not equal</returns>
        public static bool operator !=(Rectangle a, Rectangle b) {
            return !(a == b);
        }
    }
}
